from subprocess import PIPE, Popen
import glob
from Utils import run_command_async
import time
import os
import platform
from Utils import work_logger, run_command_sync, run_command_async, get_rid


@work_logger(os.path.join(os.getcwd(), f'Tips\{__name__}.log'))
def create_consoleapp(floder, sdk_version, source_feed, log_path):
    '''Create and publish a dotnet console app.

    The console app is used to test startup feature of
        dotnet-counters/dotnet-trace.

    Return:
        return Result class
    '''
    if int(sdk_version) == 3:
        message = f'ignore consoleapp: new feature isn\'t supported by .net core 3.1.\n'
        print(message)
        with open(log_path, 'a+') as f:
            f.write(message)
        return
    project_dir = os.path.join(floder, 'consoleapp')
    rt_code = run_command_sync(
        f'dotnet new console -o {project_dir}', cwd=floder, log_path=log_path)
    if rt_code != 0:
        message = 'Fail to create console app!\n'
        print(message)
        with open(log_path, 'a+')as log:
            log.write(message)
        return
    machine_type = platform.machine().lower()
    print(machine_type)

    if source_feed != '':
        rt_code = run_command_sync(
            f'dotnet build -o out -r {get_rid()} --sel-contained --source {source_feed}', cwd=project_dir, log_path=log_path)
        if rt_code == 0:
            message = 'successfully create and build console app\n'
            print(message)
            with open(log_path, 'a+') as log:
                log.write(message)
            return
    # if given runtime isn't available, try to publish without specifying rid.
    rt_code = run_command_sync(
        f'dotnet publish -o out -r {get_rid()}',
        cwd=project_dir,
        log_path=log_path
    )
    if rt_code != 0:
        rt_code = run_command_sync(
            f'dotnet publish -o out',
            cwd=project_dir,
            log_path=log_path
        )
    if rt_code != 0:
        message = 'fail to build console app!\n'
    else:
        message = 'successfully create and build console app.\n'

    print(message)
    with open(log_path, 'a+') as log:
        log.write(message)


@work_logger(os.path.join(os.getcwd(), f'Tips\{__name__}.log'))
def create_webapp(floder, source_feed, log_path):
    project_dir = os.path.join(floder, 'webapp')
    rt_code = run_command_sync(
        f'dotnet new webapp -o {project_dir}', cwd=floder, log_path=log_path)
    if rt_code != 0:
        message = 'Fail to create webapp!\n'
        print(message)
        with open(log_path, 'a+')as log:
            log.write(message)
        return
    if source_feed != '':
        rt_code = run_command_sync(
            f'dotnet build -o out -r {get_rid()} --sel-contained --source {source_feed}', cwd=project_dir, log_path=log_path)
        if rt_code == 0:
            message = 'successfully create and build webapp!\n'
            print(message)
            with open(log_path, 'a+') as log:
                log.write(message)
            return
     # if given runtime isn't available, try to publish without specifying rid.
    rt_code = run_command_sync(
        f'dotnet publish -o out -r {get_rid()}',
        cwd=project_dir,
        log_path=log_path
    )
    if rt_code != 0:
        rt_code = run_command_sync(
            f'dotnet publish -o out',
            cwd=project_dir,
            log_path=log_path
        )

    if rt_code != 0:
        message = 'fail to publish webapp.\n'
    else:
        message = 'successfully create and build webapp.\n'

    print(message)
    with open(log_path, 'a+') as log:
        log.write(message)


def run_webapp(project_dir: str):
    tmp_path = os.path.join(project_dir, 'tmp')
    tmp_write = open(tmp_path, 'w+')
    tmp_read = open(tmp_path, 'r')
    if 'win' in get_rid():
        bin_extension = '.exe'
    else:
        bin_extension = ''
    if f'webapp{bin_extension}' in os.listdir(f'{project_dir}/out'):
        proc = run_command_async(
            f'{project_dir}/out/webapp{bin_extension}', stdout=tmp_write)
    else:
        proc = run_command_async(
            f'dotnet {project_dir}/out/webapp.dll', stdout=tmp_write)
    while True:
        if 'Application started' in tmp_read.read():
            print('webapp is running!')
            tmp_read.close()
            break
        else:
            time.sleep(2)
    tmp_write.close()
    return proc


@work_logger(os.path.join(os.getcwd(), f'Tips\{__name__}.log'))
def test_dotnet_dump(floder, log_path):
    if 'osx' in get_rid():
        message = 'dotnet-dump doesn\'t support on osx.\n'
        print(message)
        with open(log_path, 'a+') as f:
            f.write(message)
        return
    # if run_webapp is False:
    #     message = f'can\'t run webapp for dotnet-dump.\n'
    #     print(message)
    #     with open(log_path, 'a+') as f:
    #         f.write(message)
    #     return
    webapp_dir = os.path.join(floder, 'webapp')
    webapp = run_webapp(webapp_dir)
    sync_commands_list = ['dotnet-dump --help',
                          'dotnet-dump ps', f'dotnet-dump collect -p {webapp.pid}']  # pid进程号
    for command in sync_commands_list:
        run_command_sync(command, log_path, cwd=floder)
    webapp.terminate()
    webapp.communicate()

    if 'win' in get_rid():
        dump_paths = glob.glob(f'{floder}/dump*.dmp')
    else:
        dump_paths = glob.glob(f'{floder}/core_*')

    if len(dump_paths) == 0:
        message = f'no dump files available.\n'
        print(message)
        with open(log_path, 'a+') as f:
            f.write(message)
        return

    analyze_commands = [
        b'clrstack\n',
        b'clrthreads\n',
        b'clrmodules\n',
        b'eeheap\n',
        b'dumpheap\n',
        b'dso\n',
        b'eeversion\n',
        b'exit\n'
    ]
    analyze_output_path = os.path.join(floder, 'dotnet_analyze.log')
    with open(analyze_output_path, 'w+') as f:
        proc = run_command_async(
            f'dotnet-dump analyze {dump_paths[0]}',
            floder,
            stdin=PIPE,
            stdout=f,
            stderr=f
        )
        for command in analyze_commands:
            try:
                proc.stdin.write(command)
            except Exception as exception:
                f.write(f'{exception}\n'.encode('utf-8'))
                continue
        proc.communicate()
